import { extend } from "ol/extent";

/**
 * @abstract 平移到指定位置
 * @param {*} map
 * @param {*} center 要移动到的中心点
 * @param {*} duration 动画持续时间
 * @param {*} done 动画结束后的回调函数
 */
export const panTo = (map, { position, duration = 1000 }, done = () => {}) => {
  map.getView().animate(
    {
      center: position,
      duration,
    },
    done
  );
};

/**
 * @abstract 向某个方向平移一段距离
 * @param {*} map
 * @param {number} distance 要平移的距离
 * @param { 'top' | 'bottom' | 'left' | 'right'  | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | number } direction 要平移的方向  如果参数为一个数字则表示方位角（以度为单位）
 * @param { 'px' | 'm' | 'km' } unit 距离的单位
 * @param {number} duration 动画持续时间
 * @param {function} done 动画结束后的回调函数
 */
export const pan = (
  map,
  { distance, direction, unit = "m", duration = 1000 },
  done = () => {}
) => {
  /*
    基本思路:
    1. 获取当前视图中心点坐标
    2. 将当前的中心点坐标 和 需要移动的距离 都转换为以像素为单位的屏幕像素坐标与屏幕像素距离
    3. 根据方向计算新的中心点坐标
    4. 将新的中心点坐标重新转换为地理坐标
    5. 执行平移动画
  */

  // 获取当前视图中心点坐标
  const view = map.getView();
  const center = view.getCenter();
  const resolution = view.getResolution();

  // 将中心点坐标转换为像素坐标
  const centerPixel = map.getPixelFromCoordinate(center);

  // 将需要移动的距离转换为像素距离
  let finalDistance = distance;
  if (unit === "km") {
    finalDistance =
      (distance * 1000) /
      (resolution * view.getProjection().getMetersPerUnit());
  } else if (unit === "m") {
    finalDistance =
      distance / (resolution * view.getProjection().getMetersPerUnit());
  }

  // 计算方位角
  let alpha;
  if (typeof direction === "number") {
    alpha = direction * (Math.PI / 180);
  } else {
    switch (direction) {
      case "top":
        alpha = 270 * (Math.PI / 180);
        break;
      case "bottom":
        alpha = 90 * (Math.PI / 180);
        break;
      case "left":
        alpha = 180 * (Math.PI / 180);
        break;
      case "right":
        alpha = 0;
        break;
      case "top-left":
        alpha = 225 * (Math.PI / 180);
        break;
      case "top-right":
        alpha = 135 * (Math.PI / 180);
        break;
      case "bottom-left":
        alpha = 315 * (Math.PI / 180);
        break;
      case "bottom-right":
        alpha = 45 * (Math.PI / 180);
        break;
      default:
        return;
    }
  }

  // 根据方向计算新的中心点坐标
  let newCenterPixel = [];

  newCenterPixel[0] = centerPixel[0] + finalDistance * Math.cos(alpha);
  newCenterPixel[1] = centerPixel[1] + finalDistance * Math.sin(alpha);

  // 将新的中心点坐标转换为地理坐标
  const newCenter = map.getCoordinateFromPixel(newCenterPixel);

  // 执行平移动画
  view.animate(
    {
      center: newCenter,
      duration: duration,
    },
    done
  );
};

/**
 * @abstract 缩放到指定缩放级别
 * @param {*} map
 * @param {*} zoom 要缩放到的缩放级别
 * @param {*} duration 动画持续时间
 * @param {*} done 动画结束后的回调函数
 */
export const zoomTo = (map, { zoom, duration = 1000 }, done = () => {}) => {
  map.getView().animate(
    {
      zoom,
      duration,
    },
    done
  );
};

/**
 * @abstract 旋转到指定角度
 * @param {*} map
 * @param {*} rotation 要旋转到的角度 (以度为单位)
 * @param {*} duration 动画持续时间
 * @param {*} anchor 旋转的锚点
 * @param {*} done 动画结束后的回调函数
 */
export const rotateTo = (
  map,
  { rotation, duration = 1000, anchor },
  done = () => {}
) => {
  map.getView().animate(
    {
      rotation: rotation * (Math.PI / 180),
      duration,
    },
    done
  );
};

/**
 * @abstract 当前旋转角度增加指定角度
 * @param {*} map
 * @param {*} rotation 要增加的角度 (以度为单位)
 * @param {*} duration 动画持续时间
 * @param {*} anchor 旋转的锚点
 * @param {*} done 动画结束后的回调函数
 */
export const rotateAdd = (
  map,
  { rotation, duration = 1000, anchor },
  done = () => {}
) => {
  const currentRotation = map.getView().getRotation();
  map.getView().animate(
    {
      rotation: currentRotation + rotation * (Math.PI / 180),
      duration,
      anchor,
    },
    done
  );
};

/**
 * @abstract 飞行
 * @param {*} map
 * @param {*} center 要移动到的中心点
 * @param {*} zoom 要缩放到的缩放级别
 * @param {*} rotation 要旋转到的角度 (以度为单位)
 * @param {*} duration 动画持续时间
 * @param {*} done 动画结束后的回调函数
 */
export const flyTo = (
  map,
  { center, zoom, rotation, duration = 1000 },
  done = () => {}
) => {
  const view = map.getView();
  center = center ?? view.getCenter();
  rotation = rotation ?? view.getRotation();
  zoom = zoom ?? view.getZoom();

  const currentZoom = view.getZoom();
  const startZoom = zoom < currentZoom ? zoom : currentZoom - 1;
  const endZoom = zoom;

  let parts = 2,
    called = false;

  const callback = complete => {
    --parts;
    if (complete && !called) {
      called = true;
      done(complete);
    }
  };

  view.animate(
    {
      center,
      rotation: rotation * (Math.PI / 180),
      duration,
    },
    callback
  );

  view.animate(
    {
      zoom: startZoom,
      duration: duration / 2,
    },
    {
      zoom: endZoom,
      duration: duration / 2,
    },
    callback
  );
};

// 弹性飞行
export const bounceTo = (
  map,
  { center, zoom, rotation },
  duration = 3000,
  done = () => {}
) => {
  const view = map.getView();
  center = center ?? view.getCenter();
  rotation = rotation ?? view.getRotation();
  zoom = zoom ?? view.getZoom();

  view.animate(
    {
      center,
      zoom,
      rotation,
      duration,
      easing: bounce,
    },
    done
  );
};

//
function bounce(t) {
  const s = 7.5625;
  const p = 2.75;
  let l;
  if (t < 1 / p) {
    l = s * t * t;
  } else {
    if (t < 2 / p) {
      t -= 1.5 / p;
      l = s * t * t + 0.75;
    } else {
      if (t < 2.5 / p) {
        t -= 2.25 / p;
        l = s * t * t + 0.9375;
      } else {
        t -= 2.625 / p;
        l = s * t * t + 0.984375;
      }
    }
  }
  return l;
}

/**
 * @abstract 定位到几何图形
 * @param {*} map
 * @param {*} feature 要定位的几何图形
 * @param {*} duration 动画持续时间
 * @param {*} maxZoom 最大缩放级别
 * @param {*} done 动画结束后的回调函数
 */
export const flyToFeature = (
  map,
  feature,
  duration = 1000,
  maxZoom = 15,
  done = () => {}
) => {
  map.getView().fit(feature.getGeometry(), {
    duration,
    padding: [100, 100, 100, 100],
    paddingFromRotation: true,
    paddingFromCenter: true,
    maxZoom,
    callback: done,
  });
};

/**
 * @abstract 定位到多个几何图形
 * @param {*} map  
 * @param {*} features  要定位的多个几何图形
 * @param {*} duration 动画持续时间
 * @param {*} maxZoom 最大缩放级别
 * @param {*} done 动画结束后的回调函数
 */
export const flyToFeatures = (
  map,
  features,
  duration = 1000,
  maxZoom = 15,
  done = () => {}
) => {
  const extent = features
    .map(f => f.getGeometry().getExtent())
    .reduce((acc, e) => extend(acc, e));

  map.getView().fit(extent, {
    duration,
    padding: [100, 100, 100, 100],
    paddingFromRotation: true,
    paddingFromCenter: true,
    maxZoom,
    callback: done,
  });
};
